/*
 * Copyright 2015-2016 fududu.org Team.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.fududu.oauth2;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;

import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * @author Fuchun
 * @since 1.0
 */
public abstract class AbstractOAuth2Request implements OAuth2Request {

    private static final long serialVersionUID = 1L;

    protected String clientId;
    protected String clientSecret;
    protected String redirectUri;
    protected String scope;
    protected Map<String, String> customParams;

    /**
     * The OAuth2.0 request query string.
     */
    protected transient String queryString;

    protected AbstractOAuth2Request() {
        super();
    }

    protected AbstractOAuth2Request(String clientId, String clientSecret, String redirectUri, String scope) {
        super();
        this.clientId = clientId;
        this.clientSecret = clientSecret;
        this.redirectUri = redirectUri;
        this.scope = scope;
    }

    protected AbstractOAuth2Request(String queryString) {
        super();
        this.queryString = queryString;
        parseQueryString();
    }

    protected Map<String, String> parseQueryString() {
        if (queryString == null || queryString.isEmpty()) {
            return ImmutableMap.of();
        }
        Map<String, String> keyValueMap = QS_SPLITTER.split(queryString);
        clientId = keyValueMap.get(OAuth2.CLIENT_ID);
        clientSecret = keyValueMap.get(OAuth2.CLIENT_SECRET);
        String redirectUri = keyValueMap.get(OAuth2.REDIRECT_URI);
        if (redirectUri != null && redirectUri.length() > 0) {
            this.redirectUri = OAuth2Request.decodeUrl(redirectUri);
        }
        scope = keyValueMap.get(OAuth2.SCOPE);

        if (customParams != null && customParams.size() > 0) {
            customParams.clear();
        }
        return keyValueMap;
    }

    @Override
    @JsonProperty(OAuth2.CLIENT_ID)
    public String getClientId() {
        return clientId;
    }

    public void setClientId(String clientId) {
        this.clientId = clientId;
    }

    @Override
    public String getClientSecret() {
        return clientSecret;
    }

    public void setClientSecret(String clientSecret) {
        this.clientSecret = clientSecret;
    }

    @Override
    @JsonProperty(OAuth2.REDIRECT_URI)
    public String getRedirectUri() {
        return redirectUri;
    }

    public void setRedirectUri(String redirectUri) {
        this.redirectUri = redirectUri;
    }

    @Override
    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    @Override
    public Map<String, String> getCustomParams() {
        return customParams;
    }

    public void setCustomParams(Map<String, String> customParams) {
        this.customParams = customParams;
    }

    public <E extends AbstractOAuth2Request> E addCustomParam(String name, String value) {
        checkNotNull(name, "custom parameter name");
        value = firstNonNull(value, "");
        if (customParams == null) {
            customParams = new LinkedHashMap<>();
        }
        customParams.put(name, value);
        @SuppressWarnings("unchecked")
        E e = (E) this;
        return e;
    }

    @Override
    public String getQueryString() {
        return getQueryString(null);
    }

    protected String getQueryString(Collection<String> excludes) {
        if (queryString != null) {
            return queryString;
        }
        String encodedRedirectUri = OAuth2Request.encodeUrl(getRedirectUri());
        Map<String, String> keyValueMap = new LinkedHashMap<>();
        addPrevParam(keyValueMap);
        if (excludes != null) {
            if (!excludes.contains(OAuth2.CLIENT_ID)) {
                keyValueMap.put(OAuth2.CLIENT_ID, getClientId());
            }
            if (!excludes.contains(OAuth2.CLIENT_SECRET) &&
                    getClientSecret() != null && getClientSecret().length() > 0) {
                keyValueMap.put(OAuth2.CLIENT_SECRET, getClientSecret());
            }
        } else {
            keyValueMap.put(OAuth2.CLIENT_ID, getClientId());
            if (getClientSecret() != null && getClientSecret().length() > 0)
                keyValueMap.put(OAuth2.CLIENT_SECRET, getClientSecret());
        }

        keyValueMap.put(OAuth2.REDIRECT_URI, encodedRedirectUri);
        keyValueMap.put(OAuth2.SCOPE, getScope());
        addLastParam(keyValueMap);
        if (customParams != null && customParams.size() > 0) {
            keyValueMap.putAll(customParams);
        }
        queryString = QS_JOINER.join(keyValueMap);
        return queryString;
    }

    public void setQueryString(String queryString) {
        this.queryString = queryString == null ? null : queryString.trim();
        if (this.queryString == null || this.queryString.isEmpty()) {
            return;
        }
        parseQueryString();
    }

    protected void addPrevParam(final Map<String, String> keyValueMap) {}

    protected void addLastParam(final Map<String, String> keyValueMap) {}

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof AbstractOAuth2Request)) return false;

        AbstractOAuth2Request that = (AbstractOAuth2Request) o;

        return Objects.equals(clientId, that.clientId) &&
                Objects.equals(clientSecret, that.clientSecret) &&
                Objects.equals(redirectUri, that.redirectUri) &&
                Objects.equals(scope, that.scope) &&
                Objects.equals(customParams, that.customParams);
    }

    @Override
    public int hashCode() {
        int result = clientId != null ? clientId.hashCode() : 0;
        result = 31 * result + (clientSecret != null ? clientSecret.hashCode() : 0);
        result = 31 * result + (redirectUri != null ? redirectUri.hashCode() : 0);
        result = 31 * result + (scope != null ? scope.hashCode() : 0);
        result = 31 * result + (customParams != null ? customParams.hashCode() : 0);
        return result;
    }

    protected MoreObjects.ToStringHelper toStringHelper(Map<String, Object> prev, Map<String, Object> last) {
        MoreObjects.ToStringHelper tsh = MoreObjects.toStringHelper(getClass());
        if (prev != null && prev.size() > 0) {
            prev.forEach(tsh::add);
        }
        tsh.add("clientId", getClientId())
                .add("clientSecret", getClientSecret())
                .add("redirectUri", getRedirectUri())
                .add("scope", getScope());
        if (last != null && last.size() > 0) {
            last.forEach(tsh::add);
        }
        tsh.add("customParams", getCustomParams());
        return tsh;
    }

    @Override
    public String toString() {
        return toStringHelper(null, null).toString();
    }
}
